(in-package :timeclock)

(defclass employee ()
  ((first-name :initarg :first-name
	       :accessor first-name
	       :initform nil)
   (last-name :initarg :last-name
	      :accessor last-name
	      :initform nil)
   (hourly-wage :initarg :hourly-wage
		:accessor hourly-wage
		:documentation "Hourly wage, in cents"
		:initform 600)
   (current-time-interval :accessor current-time-interval
			  :initform nil
			  :documentation "The time interval which is now
underway. Its beginning time is set when the employee punches in, and its
ending time is set when the employee punches out. It is then added to the
time-intervals list")
   (time-intervals :initform '()
		   :accessor time-intervals
		   :documentation "A list of time-intervals during which the
employee was punched in")
   (total-time-lazy :initform 0
		    :accessor total-time-lazy
		    :documentation "A lazily updated slot containing the
total-time of an employee")
   (in-p :initform nil
	 :accessor in-p
	 :documentation "Is the employee clocked in right now?")))

(defmethod print-object ((employee employee) stream)
  (print-unreadable-object (employee stream :type t)
    (format stream "~A, $~$/hour (~:[out~;in~]))"
	    (name employee)
	    (/ (hourly-wage employee) 100)
	    (in-p employee))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defgeneric name (employee)
  (:documentation "A employee's full name"))

(defmethod name ((employee employee))
  (with-slots (first-name last-name) employee
    (format nil "~A ~A" first-name last-name)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-presentation-type employee ())

(define-presentation-method present (employee (type employee) stream
				     (view textual-view) &key)
  (formatting-row (stream)
    (formatting-cell (stream)
      (write-string (name employee) stream))
    (formatting-cell (stream)
      (format stream "(~:[out~;in~])"
	      (in-p employee)))
    (formatting-cell (stream)
      (format stream "~A"
	      (total-time-lazy employee)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defgeneric punch (employee)
  (:documentation "Punch an employee in or out, depending on current state"))

(defgeneric punch-in (employee)
  (:documentation "Punch an employee in"))

(defgeneric punch-out (employee)
  (:documentation "Punch an employee out"))

(defmethod punch ((employee employee))
  (if (in-p employee)
      (punch-out employee)
      (punch-in employee)))

(defmethod punch-in ((employee employee))
  (print 'in)
  (with-accessors ((in-p in-p)
		   (current-time-interval current-time-interval)) employee
    (if (not in-p)
	(progn
	  (setf in-p t)
	  (setf current-time-interval (make-instance 'time-interval))
	  (start current-time-interval))
	(warn "~S is already punched in" employee))))

(defmethod punch-out ((employee employee))
  (print 'pout)
  (with-accessors ((in-p in-p)
		   (current-time-interval current-time-interval)
		   (time-intervals time-intervals)) employee
    (if in-p
	(progn
	  (setf in-p nil)
	  (stop current-time-interval)
	  (push current-time-interval time-intervals)
	  (setf current-time-interval nil)
	  (update-total-time-lazy employee))
	(warn "~S is already punched out" employee))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defgeneric total-time (employee)
  (:documentation "The total time, in seconds, that an employee has worked"))

(defmethod total-time (employee)
  (reduce #'+ (mapcar 'duration (time-intervals employee))))

(defgeneric update-total-time-lazy (employee)
  (:documentation "Bring total-time-lazy up to date"))

(defmethod update-total-time-lazy ((employee employee))
  (with-accessors ((total-time-lazy total-time-lazy)) employee
    (setf total-time-lazy (total-time employee))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(make-instance 'employee :first-name "Larry" :last-name "Stooge" :hourly-wage 350)

